// 存放数据  data, computed, watch
import observe from "./observe/index.js";
import Watcher from "./observe/watcher.js";
import Dep from "./observe/dep";
export function initState(vm) {
  const opts = vm.$options;
  if (opts.data) {
    initData(vm);
  }
  if (opts.watch) {
    initWatch(vm);
  }
  if (opts.computed) {
    initComputed(vm);
  }
  if (opts.methods) {
    initMethods(vm);
  }
}

// 数据代理
function proxy(vm, source, key) {
  // let value = vm[source][key];  // 不能使用此行，会将 value 缓存起来，get 时拿不到最新值
  Object.defineProperty(vm, key, {
    get() {
      return vm[source][key]; // 保证每次都是最新值， 与 _data 中同步
    },
    set(newVal) {
      vm[source][key] = newVal; // 不能用 value = newVal,用了就不能让 vm._data[key] 与 vm[key] 同步
    },
  });
}

function initWatch(vm) {
  let watch = vm.$options.watch;
  for (let key in watch) {
    const handler = watch[key];
    if (Array.isArray(handler)) {
      // 数组
      handler.forEach((handle) => {
        createWatcher(vm, key, handle);
      });
    } else {
      createWatcher(vm, key, handler); // 对象，函数，字符串
    }
  }
}

// 会根据依赖的数据
// 内部也使用了 defineProperty， 取值就会执行, 不取不执行
// 第二种 是 get 和 set 方式
// 内部有一个变量,  dirty
// 本质是一个 watcher, 内部依赖的属性会收集此 watcher
function initComputed(vm) {
  let computed = vm.$options.computed;
  const watchers = (vm._computedWatchers = {}); // 用来存放 computed 计算属性
  for (let key in computed) {
    const userDef = computed[key];
    const getter = typeof userDef === "function" ? userDef : userDef.get; // 两种使用方式

    // lazy: 不取值时就不执行
    watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true });
    defineComputed(vm, key, userDef);
  }
}

function defineComputed(target, key, userDef) {
  // 计算属性的描述符
  const sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    set: () => {},
    get: () => {},
  };
  if (typeof userDef === "function") {
    // 计算属性为普通函数
    sharedPropertyDefinition.get = createComputedGetter(key);
  } else {
    // 计算属性中有 get 和 set
    sharedPropertyDefinition.get = createComputedGetter(key); // 需要加缓存
    sharedPropertyDefinition.set = userDef.set;
  }

  Object.defineProperty(target, key, sharedPropertyDefinition); // 将计算属性添加到 vm
}

// 由 dirty 来决定是否调用
// 当使用 vm.fullName 来取计算属性值时，实际就是调用此方法, 此方法就是 vm 对象上的一个方法, this 即为 vm
function createComputedGetter(key) {
  // 最终抵用的方法
  return function () {
    const watcher = this._computedWatchers[key]; // 此处 this  为 vm
    // 决定是否重新执行,dirty 默认为 true
    // if (watcher) {
    if (watcher.dirty) {
      watcher.evaluate();
    }

    // 页面模板没有使用到计算属性时，不会进入此判断
    // 当页面模板中使用了计算属性时，就必然会进入判断，外层为渲染 watcher, 内存为计算属性 watcher,栈一层一层弹出，指针会从内向外移动
    // 计算属性取值完毕后，若指针所指还有 watcher, 说明此时指针处在外层渲染 watcher 中
    // 如果有计算属性依赖计算属性时，会有更多层栈

    // 此时 Dep.target 应为模板的渲染 watcher
    if (Dep.target && !Dep.target.lazy) {
      watcher.depend(); // 模板中的计算属性 watcher 需要收集依赖
    }
    return watcher.value;
    // }
  };
}

// handler 有可能是对象，函数，字符串，最后转为函数
// options用来 表示是用户传入的选项
function createWatcher(vm, exprOrFn, handler, options = {}) {
  // 对象
  if (typeof handler === "object") {
    options = handler;
    handler = handler.handler;
  }
  // 字符串
  if (typeof handler === "string") {
    handler = vm[handler];
  }

  // key handler, options
  // $watch 为用户 watcher 专用
  return vm.$watch(exprOrFn, handler, options);
}

function initMethods() {}

function initData(vm) {
  let data = vm.$options.data;
  // data 是函数或对象
  data = vm._data = typeof data === "function" ? data.call(vm) : data;

  // 数据代理，将 _data 中的属性都代理到 vm 上
  for (let key in data) {
    // vm[key] = data[key];
    proxy(vm, "_data", key);
  }

  // 对数据进行响应式
  observe(data);
}
